Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Constraints on generic parameters #50

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

SecondNewtonLaw
Copy link

This RFC is a possible implementation on what could be an improvement to the current generics system, by allowing generic types to be constrained, just as in any other language's implementation of generics, which would allow for generics to be used in other seemingly unrelated places, like more efficient native code generation.

rendered

@andyfriesen
Copy link
Collaborator

Constrained generics are absolutely something we're going to look at eventually, but this RFC needs a much more compelling example to properly justify them.

In your "Drawbacks" section, you point out a perfectly reasonable solution to your problem that doesn't require generics at all.

A more compelling example would be something like function foo<T: Something>(t: T): T where the type is generic and also appears in more than one part of the signature. This function is inexpressible without a constrained generic.

@SecondNewtonLaw
Copy link
Author

SecondNewtonLaw commented Jul 23, 2024

Constrained generics are absolutely something we're going to look at eventually, but this RFC needs a much more compelling example to properly justify them.

In your "Drawbacks" section, you point out a perfectly reasonable solution to your problem that doesn't require generics at all.

A more compelling example would be something like function foo<T: Something>(t: T): T where the type is generic and also appears in more than one part of the signature. This function is inexpressible without a constrained generic.

Thanks for the feedback, I have tried to add more complelling reasons, as well as more examples as to why this could be useful, some even including real world use cases, such as a Dependency Injection service container, or a factory like Instance.new having a modification that can take advantage of generics like so, still, the latter would require reflection, so its for now nothing more than a dream.

fireGun("Hello, world!") -- Type Error: type 'string' and 'Gun' are not related to one another.
```

The syntax for defining a generic function would remain the same, except, instead of simply taking a `T`, we would take a `T : TypeConstraint`, the syntax remains similar to other features of the language, and it would work by checking if the type that `T` is inferred to be has any kind of relationship with `TypeConstraint`. However, we may also want to be explicit when we are doing generics on this way, therefore a syntax to properly and explicitly specify which types the function takes in its current call would be preferred, this way, we no longer have to use turbofish to type cast into the correct return type `U` constrained by `WeaponResult`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why you're mentioning turbofish, that doesn't exist in Luau, and can't because it's ambiguous with :: <T>() -> ().

No turbofish is also not an option for the exact same reasons it's necessary in Rust.

local a, b = the<guardian, stands>(resolute)

...is perfectly valid code today.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember discussing with Drak that f.<T, U>() isn't the worst syntax in the world, but also it feels out of scope for this RFC anyway?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, it feels out of scope for this RFC to add on about, and it was already somewhat addressed on the original generic functions rfc

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

although I did not know that that snippet was valid ever, however, all I keep getting from attempting it is syntax errors

@alexmccord
Copy link
Contributor

I think I would also like to see 2 more things:

Type parameters are allowed to reference each other despite the lexical ordering, e.g. <t: Thenable<a>, a>(...) -> (...). I think this is fine because types are not a runtime construct, they're a declarative language and as such, lexical ordering isn't super important.

A where clause that comes after the bindings list, but still within the <> part because of conflict with the rest of the grammar. This is something we'll need for adding constraints on some type functions but they don't directly show up as part of the parameters or returns, meaning they're internal constraints that the caller still need to know about. <a, b where add<a, b>>(string, string) -> boolean.

Here's a full EBNF describing the potential syntax, formally:

typaram ::= <NAME> [':' ty]
typarams ::= typaram {',' typaram}
tpparam ::= <NAME> '...' [':' tp]
tpparams ::= tpparam {',' tpparam}
params ::= (typarams tpparams?) | tpparams

tybound ::= ty [':' ty]
tybounds ::= tybound {',' tybound}
tpbound ::= tp [':' tp]
tpbounds ::= tpbound {',' tpbound}
bounds ::= (tybounds tpbounds?) | tpbounds

paramdecl ::= '<' params ['where' bounds] '>'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants